﻿using System;
using System.Text;
using System.Collections.Generic;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using AnaxibiaStudios.Smug;

namespace SmugTest
{
#if DETOUR_ENABLED
  [TestClass]
  public class AdvancedUseCaseTest
  {
    [TestMethod]
    public void CallRepeatedTest()
    {
      var airplane = Create.Proxy<Airplane>();

      Prepare.Call(() => airplane.DropGear(3)).Returns(true);
      Prepare.Call(() => airplane.DropGear(6)).Returns(false);

      Assert.IsTrue(airplane.DropGear(3));
      Assert.IsFalse(airplane.DropGear(6));
      Assert.IsTrue(airplane.DropGear(3));
      Assert.IsFalse(airplane.DropGear(6));

      Verify.AllCallsTo(airplane);
    }

    [TestMethod]
    public void CallAndReplaceWithSelectionTest()
    {
      var airplane = Create.Proxy<Airplane>();

      var callPrep = Prepare.Call(() => airplane.Milleage(3)).Returns(1);
      var funcPrep = Prepare.Func<int, int>(airplane.Milleage).Returns(2);

      Assert.AreEqual(2, airplane.DropGear(1));
      Assert.AreEqual(2, airplane.DropGear(2));
      Assert.AreEqual(1, airplane.DropGear(3));
      Assert.AreEqual(2, airplane.DropGear(4));
      Assert.AreEqual(2, airplane.DropGear(5));
      Assert.AreEqual(2, airplane.DropGear(6));
      Assert.AreEqual(1, airplane.DropGear(3));

      Verify.AllCallsTo(airplane);

      Assert.AreEqual(2, callPrep.History.CallCount);
      Assert.AreEqual(5, funcPrep.History.CallCount);
    }

    [TestMethod]
    public void ChainingUpgradeTest()
    {
      var airplane = Create.Proxy<Airplane>();
      var prep = Prepare.Func<bool>(airplane.Land);

      Assert.AreEqual(prep, prep.CallCount(1).ReplaceWith(null));
      Assert.AreEqual(prep, prep.CallCount(1).Returns(true));
      Assert.AreEqual(prep, prep.CallCount(1).Throw<Exception>());
      Assert.AreEqual(prep, prep.CallCount(1).Real());
      Assert.AreEqual(prep, prep.CallCount(1).Real(() => { }));
      Assert.AreEqual(prep, prep.CallCount(1, CountRule.AtLeast).ReplaceWith(null));
      Assert.AreEqual(prep, prep.CallCount(1, CountRule.AtLeast).Returns(true));
      Assert.AreEqual(prep, prep.CallCount(1, CountRule.AtLeast).Throw<Exception>());
      Assert.AreEqual(prep, prep.CallCount(1, CountRule.AtLeast).Real());
      Assert.AreEqual(prep, prep.CallCount(1, CountRule.AtLeast).Real(() => { }));
      Assert.AreEqual(prep, prep.CalledAtLeastOnce().ReplaceWith(null));
      Assert.AreEqual(prep, prep.CalledAtLeastOnce().Returns(true));
      Assert.AreEqual(prep, prep.CalledAtLeastOnce().Throw<Exception>());
      Assert.AreEqual(prep, prep.CalledAtLeastOnce().Real());
      Assert.AreEqual(prep, prep.CalledAtLeastOnce().Real(() => { }));
      Assert.AreEqual(prep, prep.CalledOnce().ReplaceWith(null));
      Assert.AreEqual(prep, prep.CalledOnce().Returns(true));
      Assert.AreEqual(prep, prep.CalledOnce().Throw<Exception>());
      Assert.AreEqual(prep, prep.CalledOnce().Real());
      Assert.AreEqual(prep, prep.CalledOnce().Real(() => { }));


      Assert.AreEqual(prep, prep.ReplaceWith(null).CallCount(1));
      Assert.AreEqual(prep, prep.Returns(true).CallCount(1));
      Assert.AreEqual(prep, prep.Throw<Exception>().CallCount(1));
      Assert.AreEqual(prep, prep.Real().CallCount(1));
      Assert.AreEqual(prep, prep.Real(() => { }).CallCount(1));
      Assert.AreEqual(prep, prep.ReplaceWith(null).CallCount(1, CountRule.AtLeast));
      Assert.AreEqual(prep, prep.Returns(true).CallCount(1, CountRule.AtLeast));
      Assert.AreEqual(prep, prep.Throw<Exception>().CallCount(1, CountRule.AtLeast));
      Assert.AreEqual(prep, prep.Real().CallCount(1, CountRule.AtLeast));
      Assert.AreEqual(prep, prep.Real(() => { }).CallCount(1, CountRule.AtLeast));
      Assert.AreEqual(prep, prep.ReplaceWith(null).CalledAtLeastOnce());
      Assert.AreEqual(prep, prep.Returns(true).CalledAtLeastOnce());
      Assert.AreEqual(prep, prep.Throw<Exception>().CalledAtLeastOnce());
      Assert.AreEqual(prep, prep.Real().CalledAtLeastOnce());
      Assert.AreEqual(prep, prep.Real(() => { }).CalledAtLeastOnce());
      Assert.AreEqual(prep, prep.ReplaceWith(null).CalledOnce());
      Assert.AreEqual(prep, prep.Returns(true).CalledOnce());
      Assert.AreEqual(prep, prep.Throw<Exception>().CalledOnce());
      Assert.AreEqual(prep, prep.Real().CalledOnce());
      Assert.AreEqual(prep, prep.Real(() => { }).CalledOnce());
    }

    [TestMethod]
    public void ChainingUpgradeNoReturnTest()
    {
      var cat = Create.Proxy<Cat>();
      var prep = Prepare.Action(cat.Meow);

      Assert.AreEqual(prep, prep.CallCount(1).ReplaceWith(null));
      Assert.AreEqual(prep, prep.CallCount(1).Throw<Exception>());
      Assert.AreEqual(prep, prep.CallCount(1).Real());
      Assert.AreEqual(prep, prep.CallCount(1).Real(() => { }));
      Assert.AreEqual(prep, prep.CallCount(1, CountRule.AtLeast).ReplaceWith(null));
      Assert.AreEqual(prep, prep.CallCount(1, CountRule.AtLeast).Throw<Exception>());
      Assert.AreEqual(prep, prep.CallCount(1, CountRule.AtLeast).Real());
      Assert.AreEqual(prep, prep.CalledAtLeastOnce().ReplaceWith(null));
      Assert.AreEqual(prep, prep.CalledAtLeastOnce().Throw<Exception>());
      Assert.AreEqual(prep, prep.CalledAtLeastOnce().Real());
      Assert.AreEqual(prep, prep.CalledAtLeastOnce().Real(() => { }));
      Assert.AreEqual(prep, prep.CalledOnce().ReplaceWith(null));
      Assert.AreEqual(prep, prep.CalledOnce().Throw<Exception>());
      Assert.AreEqual(prep, prep.CalledOnce().Real());
      Assert.AreEqual(prep, prep.CalledOnce().Real(() => { }));

      Assert.AreEqual(prep, prep.ReplaceWith(null).CallCount(1));
      Assert.AreEqual(prep, prep.Throw<Exception>().CallCount(1));
      Assert.AreEqual(prep, prep.Real().CallCount(1));
      Assert.AreEqual(prep, prep.Real(() => { }).CallCount(1));
      Assert.AreEqual(prep, prep.ReplaceWith(null).CallCount(1, CountRule.AtLeast));
      Assert.AreEqual(prep, prep.Throw<Exception>().CallCount(1, CountRule.AtLeast));
      Assert.AreEqual(prep, prep.Real().CallCount(1, CountRule.AtLeast));
      Assert.AreEqual(prep, prep.Real(() => { }).CallCount(1, CountRule.AtLeast));
      Assert.AreEqual(prep, prep.ReplaceWith(null).CalledAtLeastOnce());
      Assert.AreEqual(prep, prep.Throw<Exception>().CalledAtLeastOnce());
      Assert.AreEqual(prep, prep.Real().CalledAtLeastOnce());
      Assert.AreEqual(prep, prep.Real(() => { }).CalledAtLeastOnce());
      Assert.AreEqual(prep, prep.ReplaceWith(null).CalledOnce());
      Assert.AreEqual(prep, prep.Throw<Exception>().CalledOnce());
      Assert.AreEqual(prep, prep.Real().CalledOnce());
      Assert.AreEqual(prep, prep.Real(() => { }).CalledOnce());
    }

    [TestMethod]
    public void CleverInheritanceTest()
    {
      var actor = new Actor();

      Assert.AreSame(actor, actor.A().B().C());
      Assert.AreSame(actor, actor.A().C().B());
      Assert.AreSame(actor, actor.C().A().B());
      Assert.AreSame(actor, actor.B().A().C());
      Assert.AreSame(actor, actor.B().C().A());
      Assert.AreSame(actor, actor.C().B().A());
    }
  }

  interface A<T> { T A(); }
  interface B<T> { T B(); }
  interface C<T> { T C(); }

  interface Empty { }
  interface NoANoB : C<Empty>, Empty { }
  interface NoANoC : B<Empty>, Empty { }
  interface NoBNoC : A<Empty>, Empty { }
  interface NoA : B<NoANoB>, C<NoANoC>, Empty { }
  interface NoB : A<NoANoB>, C<NoBNoC>, Empty { }
  interface NoC : A<NoANoC>, B<NoBNoC>, Empty { }

  class Actor : A<NoA>, B<NoB>, C<NoC>, NoA, NoB, NoC, NoANoB, NoANoC, NoBNoC, Empty
  {
    public NoA A()
    {
      return this;
    }

    public NoB B()
    {
      return this;
    }

    public NoC C()
    {
      return this;
    }

    NoANoB B<NoANoB>.B() { return (NoANoB)B(); }
    NoANoC C<NoANoC>.C() { return (NoANoC)C(); }
    NoANoB A<NoANoB>.A() { return (NoANoB)A(); }
    NoBNoC C<NoBNoC>.C() { return (NoBNoC)C(); }
    NoANoC A<NoANoC>.A() { return (NoANoC)A(); }
    NoBNoC B<NoBNoC>.B() { return (NoBNoC)B(); }
    Empty C<Empty>.C() { return C(); }
    Empty B<Empty>.B() { return B(); }
    Empty A<Empty>.A() { return A(); }
  }
#endif
}
